package org.teach.study.boot.provider;

import java.beans.Transient;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.ibatis.jdbc.SQL;
import org.quincy.rock.core.dao.EntityMetadata;
import org.quincy.rock.core.dao.IgnoreInsert;
import org.quincy.rock.core.dao.IgnoreSelect;
import org.quincy.rock.core.dao.IgnoreUpdate;
import org.quincy.rock.core.function.Function;
import org.quincy.rock.core.util.MapUtil;
import org.quincy.rock.core.util.StringUtil;
import org.quincy.rock.core.vo.Sort;

/**
 * <b>mybatis SQL语句提供者。</b>
 * <p><b>详细说明：</b></p>
 * <!-- 在此添加详细说明 -->
 * 无。
 * 
 * @version 1.0
 * @author mex2000
 * @since 1.0
 */
@SuppressWarnings({ "rawtypes", "unchecked" })
public class MybatisSQLProvider {
	public static final String ENTITY_TYPE_VAR_NAME = "entityType";
	public static final String ENTITY_VAR_NAME = "entity";
	public static final String VO_MAP_VAR_NAME = "voMap";
	public static final String OFFSET_VAR_NAME = "offset";
	public static final String LIMIT_VAR_NAME = "limit";
	public static final String NAME_VAR_NAME = "name";
	public static final String VALUE_VAR_NAME = "value";
	public static final String IGNORE_ID_VAR_NAME = "ignoreId";
	public static final String SORT_VAR_NAME = "sort";
	public static final String TABLE_PREFIX = "t_";
	public static final String TABLE_SUFFIX = "s";
	public static final String FIELD_PREFIX = "f_";
	public static final String ID_FIELD_NAME = "f_id";
	public static final char SEPARATOR = StringUtil.CHAR_UNDERLINE;

	public static final Function<String, String, RuntimeException> CONV_TABLE_NAME = (objName) -> StringUtil
			.objName2DbName(objName, TABLE_PREFIX, SEPARATOR).concat(TABLE_SUFFIX);
	public static final Function<String, String, RuntimeException> CONV_FIELD_NAME = (objName) -> StringUtil
			.objName2DbName(objName, FIELD_PREFIX, SEPARATOR);

	private final static Map<String, EntityMetadata> entiMataMap = new HashMap<>();

	public String deleteById(Map<String, Object> paramMap) {
		Class clazz = MapUtil.getObject(paramMap, ENTITY_TYPE_VAR_NAME);
		String tableName = CONV_TABLE_NAME.call(clazz.getSimpleName());
		CharSequence where = StringUtil.concat(ID_FIELD_NAME, "=#{id}");
		SQL sql = new SQL().DELETE_FROM(tableName).WHERE(where.toString());
		return sql.toString();
	}

	public String deleteMoreById(Map<String, Object> paramMap) {
		Class clazz = MapUtil.getObject(paramMap, ENTITY_TYPE_VAR_NAME);
		Iterator<? extends Number> it = ((Iterable) paramMap.get("ids")).iterator();
		String tableName = CONV_TABLE_NAME.call(clazz.getSimpleName());
		//
		SQL sql = new SQL().DELETE_FROM(tableName).WHERE(whereInById(it));
		return sql.toString();
	}

	public String deleteAll(Class<?> clazz) {
		String tableName = CONV_TABLE_NAME.call(clazz.getSimpleName());
		SQL sql = new SQL().DELETE_FROM(tableName);
		return sql.toString();
	}

	public String total(Class<?> clazz) {
		String tableName = CONV_TABLE_NAME.call(clazz.getSimpleName());
		return "select count(*) from " + tableName;
	}

	public String findById(Map<String, Object> paramMap) {
		Class clazz = MapUtil.getObject(paramMap, ENTITY_TYPE_VAR_NAME);
		String tableName = CONV_TABLE_NAME.call(clazz.getSimpleName());
		String[] cols = selectColumns(clazz);
		CharSequence where = StringUtil.concat(ID_FIELD_NAME, "=#{id}");
		SQL sql = new SQL().FROM(tableName).SELECT(cols).WHERE(where.toString());
		return sql.toString();
	}

	public String findMoreById(Map<String, Object> paramMap) {
		Class clazz = MapUtil.getObject(paramMap, ENTITY_TYPE_VAR_NAME);
		String tableName = CONV_TABLE_NAME.call(clazz.getSimpleName());
		String[] cols = selectColumns(clazz);
		Iterator<? extends Number> it = ((Iterable) paramMap.get("ids")).iterator();
		SQL sql = new SQL().FROM(tableName).SELECT(cols).WHERE(whereInById(it));
		return sql.toString();
	}

	public String findAll(Map<String, Object> paramMap) {
		Class clazz = MapUtil.getObject(paramMap, ENTITY_TYPE_VAR_NAME);
		String tableName = CONV_TABLE_NAME.call(clazz.getSimpleName());
		Sort sort = MapUtil.getObject(paramMap, SORT_VAR_NAME);
		String[] cols = selectColumns(clazz);
		SQL sql = new SQL().FROM(tableName).SELECT(cols);
		if (Sort.isSorted(sort)) {
			String orderby = sort.toString(CONV_FIELD_NAME);
			sql = sql.ORDER_BY(orderby);
		}
		return sql.toString();
	}

	public String findAllWithLimit(Map<String, Object> paramMap) {
		Class clazz = MapUtil.getObject(paramMap, ENTITY_TYPE_VAR_NAME);
		String tableName = CONV_TABLE_NAME.call(clazz.getSimpleName());
		Sort sort = MapUtil.getObject(paramMap, SORT_VAR_NAME);
		String[] cols = selectColumns(clazz);
		long offset = MapUtil.getLong(paramMap, OFFSET_VAR_NAME);
		int limit = MapUtil.getInteger(paramMap, LIMIT_VAR_NAME);
		SQL sql = new SQL().FROM(tableName).SELECT(cols);
		if (Sort.isSorted(sort)) {
			String orderby = sort.toString(CONV_FIELD_NAME);
			sql = sql.ORDER_BY(orderby);
		}
		sql = sql.OFFSET(offset).LIMIT(limit);
		return sql.toString();
	}

	public String findByName(Map<String, Object> paramMap) {
		Class clazz = MapUtil.getObject(paramMap, ENTITY_TYPE_VAR_NAME);
		String tableName = CONV_TABLE_NAME.call(clazz.getSimpleName());
		Sort sort = paramMap.containsKey(SORT_VAR_NAME) ? MapUtil.getObject(paramMap, SORT_VAR_NAME) : null;
		String[] cols = selectColumns(clazz);
		String name = MapUtil.getObject(paramMap, NAME_VAR_NAME);
		CharSequence where = StringUtil.concat(CONV_FIELD_NAME.call(name), "=#{", VALUE_VAR_NAME, "}");
		SQL sql = new SQL().FROM(tableName).SELECT(cols).WHERE(where.toString());
		if (Sort.isSorted(sort)) {
			String orderby = sort.toString(CONV_FIELD_NAME);
			sql = sql.ORDER_BY(orderby);
		}
		return sql.toString();
	}

	public String existsById(Map<String, Object> paramMap) {
		Class clazz = MapUtil.getObject(paramMap, ENTITY_TYPE_VAR_NAME);
		String tableName = CONV_TABLE_NAME.call(clazz.getSimpleName());
		StringBuilder sb = new StringBuilder("SELECT COUNT(*) FROM ");
		sb.append(tableName);
		sb.append(" WHERE ");
		sb.append(ID_FIELD_NAME);
		sb.append("=#{id}");
		//
		return sb.toString();
	}

	public String existsByName(Map<String, Object> paramMap) {
		Class clazz = MapUtil.getObject(paramMap, ENTITY_TYPE_VAR_NAME);
		String tableName = CONV_TABLE_NAME.call(clazz.getSimpleName());
		String name = MapUtil.getObject(paramMap, NAME_VAR_NAME);
		boolean ignoreId = paramMap.containsKey(IGNORE_ID_VAR_NAME) && paramMap.get(IGNORE_ID_VAR_NAME) != null;
		StringBuilder sb = new StringBuilder("SELECT COUNT(*) FROM ");
		sb.append(tableName);
		sb.append(" WHERE ");
		sb.append(CONV_FIELD_NAME.call(name));
		sb.append("=#{");
		sb.append(VALUE_VAR_NAME);
		sb.append("}");
		if (ignoreId) {
			sb.append(" and ");
			sb.append(ID_FIELD_NAME);
			sb.append("!=#{");
			sb.append(IGNORE_ID_VAR_NAME);
			sb.append("}");
		}
		//
		return sb.toString();
	}

	public String countByIds(Map<String, Object> paramMap) {
		Class clazz = MapUtil.getObject(paramMap, ENTITY_TYPE_VAR_NAME);
		Iterator<? extends Number> it = ((Iterable) paramMap.get("ids")).iterator();
		String tableName = CONV_TABLE_NAME.call(clazz.getSimpleName());
		StringBuilder sb = new StringBuilder("SELECT COUNT(*) FROM ");
		sb.append(tableName);
		sb.append(" WHERE ");
		sb.append(whereInById(it));
		//
		return sb.toString();
	}

	public String insertOne(Map<String, Object> paramMap) {
		Object vo = MapUtil.getObject(paramMap, ENTITY_VAR_NAME);
		Class<?> clazz = vo.getClass();
		boolean ignoreNullValue = MapUtil.getBoolean(paramMap, "param2");
		String tableName = CONV_TABLE_NAME.call(clazz.getSimpleName());
		EntityMetadata meta = getEntityMetadata(clazz, Transient.class, IgnoreInsert.class);
		List<String> fList = new ArrayList<>(meta.propertyCount());
		List<String> vList = new ArrayList<>(meta.propertyCount());
		for (String name : meta.persistNames()) {
			if (!meta.canRead(name))
				continue;
			boolean insert = false;
			if ("id".equals(name)) {
				if (!meta.isNullPropertyValue(vo, name)) {
					insert = true;
				}
			} else if (ignoreNullValue) {
				if (!meta.isNullPropertyValue(vo, name)) {
					insert = true;
				}
			} else {
				insert = true;
			}
			if (insert) {
				fList.add(CONV_FIELD_NAME.call(name));
				vList.add(StringUtil.concat("#{", ENTITY_VAR_NAME, ".", name, "}").toString());
			}
		}
		SQL sql = new SQL().INSERT_INTO(tableName).INTO_COLUMNS(fList.toArray(ArrayUtils.EMPTY_STRING_ARRAY))
				.INTO_VALUES(vList.toArray(ArrayUtils.EMPTY_STRING_ARRAY));

		//
		return sql.toString();
	}

	public String updateOne(Map<String, Object> paramMap) {
		Object vo = MapUtil.getObject(paramMap, ENTITY_VAR_NAME);
		Class<?> clazz = vo.getClass();
		boolean ignoreNullValue = MapUtil.getBoolean(paramMap, "param2");
		String tableName = CONV_TABLE_NAME.call(clazz.getSimpleName());
		EntityMetadata meta = getEntityMetadata(clazz, Transient.class, IgnoreUpdate.class);
		List<String> list = new ArrayList<>(meta.propertyCount());
		for (String name : meta.persistNames()) {
			if ("id".equals(name) || !meta.canRead(name))
				continue;
			boolean update = false;
			if (ignoreNullValue) {
				update = !meta.isNullPropertyValue(vo, name);
			} else {
				update = true;
			}
			if (update) {
				CharSequence setV = StringUtil.concat(CONV_FIELD_NAME.call(name), "=#{", ENTITY_VAR_NAME, ".", name,
						"}");
				list.add(setV.toString());
			}
		}
		CharSequence where = StringUtil.concat(ID_FIELD_NAME, "=#{", ENTITY_VAR_NAME, ".id}");
		SQL sql = new SQL().UPDATE(tableName).SET(list.toArray(ArrayUtils.EMPTY_STRING_ARRAY)).WHERE(where.toString());
		return sql.toString();
	}

	public String updateSpecific(Map<String, Object> paramMap) {
		Class clazz = MapUtil.getObject(paramMap, ENTITY_TYPE_VAR_NAME);
		String tableName = CONV_TABLE_NAME.call(clazz.getSimpleName());
		Map<String, Object> partMap = MapUtil.getObject(paramMap, VO_MAP_VAR_NAME);
		List<String> list = new ArrayList<>(partMap.size());
		for (String name : partMap.keySet()) {
			if (!"id".equals(name)) {
				CharSequence setV = StringUtil.concat(CONV_FIELD_NAME.call(name), "=#{", VO_MAP_VAR_NAME, ".", name,
						"}");
				list.add(setV.toString());
			}
		}
		CharSequence where = StringUtil.concat(ID_FIELD_NAME, "=#{", VO_MAP_VAR_NAME, ".id}");
		SQL sql = new SQL().UPDATE(tableName).SET(list.toArray(ArrayUtils.EMPTY_STRING_ARRAY)).WHERE(where.toString());
		return sql.toString();
	}

	private static EntityMetadata getEntityMetadata(Class clazz, Class<? extends Annotation>... transientClasses) {
		String key;
		if (ArrayUtils.isEmpty(transientClasses)) {
			key = clazz.getName();
		} else {
			StringBuilder sb = new StringBuilder(clazz.getName());
			for (Class<? extends Annotation> transientClass : transientClasses) {
				sb.append(StringUtil.CHAR_COLON);
				sb.append(transientClass.getSimpleName());
			}
			key = sb.toString();
		}
		EntityMetadata meta = entiMataMap.get(key);
		if (meta == null) {
			meta = new EntityMetadata(clazz, transientClasses);
			entiMataMap.put(key, meta);
		}
		return meta;
	}

	private static String[] selectColumns(Class clazz) {
		EntityMetadata meta = getEntityMetadata(clazz, Transient.class, IgnoreSelect.class);
		List<String> list = new ArrayList<>(meta.propertyCount());
		for (String name : meta.persistNames()) {
			if (meta.canWrite(name)) {
				list.add(CONV_FIELD_NAME.call(name));
			}
		}
		return list.toArray(new String[list.size()]);
	}

	private static String whereInById(Iterator<? extends Number> it) {
		StringBuilder where = new StringBuilder(ID_FIELD_NAME);
		where.append(" IN (");
		if (it.hasNext()) {
			where.append(it.next().longValue());
		}
		while (it.hasNext()) {
			where.append(',');
			where.append(it.next().longValue());
		}
		where.append(")");
		return where.toString();
	}
}
